// $codepro.audit.disable booleanMethodNamingConvention
/**
 * Project: Utilities <br>
 * Package: org.leumasjaffe.collections
 */
package org.leumasjaffe.collections;

import static org.leumasjaffe.functional.CompareOperator.Compare.LESS_THAN_OR_EQUAL;
import static org.leumasjaffe.functional.CompareOperator.Compare.GREATER_THAN_OR_EQUAL;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.leumasjaffe.functional.BinaryFunction;
import org.leumasjaffe.functional.BinaryOperator;
import org.leumasjaffe.functional.CompareOperator;
import org.leumasjaffe.functional.Predicate;
import org.leumasjaffe.functional.Consumer;
import org.leumasjaffe.functional.UnaryOperator;
import org.leumasjaffe.functional.UnaryFunction;
import org.leumasjaffe.functional.lazy.LazyEval;

import com.google.common.base.Objects;
import com.google.common.base.Optional;

/**
 * Class org.leumasjaffe.collections..Collect in file Collect.java <br>
 * 
 * @author leumasjaffe
 * @version May 25, 2013
 * Created on: May 25, 2013
 * 
 */
@NonNullByDefault
public final class Collect {
	
	/**
	 * Class org.leumasjaffe.collections.Collect.Lazy in file Collect.java <br>
	 * A class that affords access to lazy evaluation style functions of the same type that
	 * are provided by the Collect class.
	 * 
	 * @author leumasjaffe
	 * @version Nov 29, 2013
	 * Created on: Nov 29, 2013
	 * 
	 */
	public static final class Lazy {
		/**
		 * Generates a lazy-evaluation wrapper
		 * @param col A Collection to perform operations of lazily
		 * @param <T> The type of the elements being fed into the evaluation
		 * @return A lazy-evaluator for the given collection
		 */
		public static <T> LazyEval<T> from(Collection<T> col) {
			return org.leumasjaffe.functional.lazy.Lazy.from(col);
		}
		
		/**
		 * Generates a lazy-evaluation wrapper
		 * @param elt A single element to perform operations of lazily
		 * @param <T> The type of the elements being fed into the evaluation
		 * @return A lazy-evaluator for the given collection
		 */
		@SuppressWarnings("null")
		public static <T> LazyEval<T> from(T elt) {
			return org.leumasjaffe.functional.lazy.Lazy.from(Collections.singletonList(elt));
		}
		
	}

	/**
	 * 
	 */
	private Collect() {
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection.
	 * @param it An Iterator to start from
	 * @param type The return class to be instantiated
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings({ "null", "rawtypes" })
	public static @Nullable <T, R> Collection<R> map(Iterator<T> it, 
			Class<? extends Collection> type, UnaryFunction<T, R> mapping) {
		@NonNull Collection<R> toMapTo = null;
		try {
			toMapTo = type.newInstance();
			
			while (it.hasNext()) {
				toMapTo.add(mapping.apply(it.next()));
			}
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		return toMapTo;
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection.
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A Collection to be mapped
	 * @param type The return class to be instantiated
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings({ "null", "rawtypes" })
	public static @Nullable <T, R> Collection<R> map(Collection<T> col, 
			Class<? extends Collection> type, UnaryFunction<T, R> mapping) {
		return map(col.iterator(), type, mapping);
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection.
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A Collection to be mapped
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings("null")
	public static @Nullable <T, R> Collection<R> map(Collection<T> col, 
			UnaryFunction<T, R> mapping) {
		return map(col, col.getClass(), mapping);
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection. <br>
	 * Specializes the map function with a return type of List
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A List to be mapped
	 * @param type The return class to be instantiated
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings({"null" })
	public static <T, R> List<R> map(List<T> col, 
			Class<? extends List<R>> type, UnaryFunction<T, R> mapping) {
		return Objects.firstNonNull((List<R>) map((Collection<T>) col, type, mapping), 
				Collections.EMPTY_LIST);
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection. <br>
	 * Specializes the map function with a return type of List
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A List to be mapped
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings("null")
	public static <T, R> List<R> map(List<T> col, UnaryFunction<T, R> mapping) {
		return (List<R>) map(col, col.getClass(), mapping);
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection. <br>
	 * Specializes the map function with a return type of Set
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A Set to be mapped
	 * @param type The return class to be instantiated
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings("null")
	public static <T, R> Set<R> map(Set<T> col, 
			Class<? extends Set<R>> type, UnaryFunction<T, R> mapping) {
		return Objects.firstNonNull((Set<R>) map((Collection<T>) col, type, mapping), 
				Collections.EMPTY_SET);
	}
	
	/**
	 * map :: ('a collection) -> ('a -> 'b) -> ('b collection) <br>
	 * Maps every element of a collection into another (or the same) type without changing the 
	 * contents of the original collection. <br>
	 * Specializes the map function with a return type of Set
	 * @see {@link org.leumasjaffe.collections.Collect#map(Iterator, Class, UnaryFunction)}
	 * @param col A Set to be mapped
	 * @param mapping A function of the form (T -> R)
	 * @param <T> The type of the input elements
	 * @param <R> The type of the output elements
	 * @return A collection of mapped values
	 */
	@SuppressWarnings("null")
	public static <T, R> Set<R> map(Set<T> col, 
			UnaryFunction<T, R> mapping) {
		return (Set<R>) map(col, col.getClass(), mapping);
	}
	
	
	/**
	 * transform :: ('a collection) -> ('a -> void) -> void <br>
	 * Mutates every element of a collection, changing its internal state. This function does not 
	 * require that the data actually be mutated, but works under the assumption that it will. <br>
	 * Note: Immutable classes like strings cannot be affected by this method, use
	 * {@link org.leumasjaffe.collections.Collect#transform(ListIterator, UnaryOperator)} instead.
	 * @param it An Iterator of data to mutate
	 * @param trans A function that hypothetically modifies the internal state of an object
	 * @param <T> The type of the input elements
	 */
	@SuppressWarnings("null")
	public static <T> void forEach(Iterator<T> it, Consumer<T> trans) {
		while (it.hasNext()) {
			trans.apply(it.next());
		}
	}
	
	/**
	 * transform :: ('a collection) -> ('a -> void) -> void <br>
	 * Mutates every element of a collection, changing its internal state. This function does not 
	 * require that the data actually be mutated, but works under the assumption that it will. <br>
	 * Note: Immutable classes like strings cannot be affected by this method, use
	 * {@link org.leumasjaffe.collections.Collect#transform(ListIterator, UnaryOperator)} instead.
	 * @param col A Collection of data to mutate
	 * @param trans A function that hypothetically modifies the internal state of an object
	 * @param <T> The type of the input elements
	 */
	@SuppressWarnings("null")
	public static <T> void forEach(Collection<T> col, Consumer<T> trans) {
		forEach(col.iterator(), trans);
	}
	
	/**
	 * transform :: ('a collection) -> ('a -> 'a) -> void <br>
	 * Mutates every element of a collection, changing its internal state. This function does not 
	 * require that the data actually be mutated, but works under the assumption that it will. <br>
	 * Note: this functions as an in-place version of {@link Collect#map(List, UnaryFunction)} for
	 * matching input and output types.
	 * @param it A ListIterator of data to mutate
	 * @param trans A function that hypothetically modifies the internal state of an object
	 * @param <T> The type of the input elements
	 */
	@SuppressWarnings("null")
	public static <T> void transform(ListIterator<T> it, UnaryOperator<T> trans) {
		while (it.hasNext()) {
			it.set(trans.apply(it.next()));
		}
	}
	
	/**
	 * transform :: ('a collection) -> ('a -> 'a) -> void <br>
	 * Mutates every element of a collection, changing its internal state. This function does not 
	 * require that the data actually be mutated, but works under the assumption that it will. <br>
	 * Note: this functions as an in-place version of {@link Collect#map(List, UnaryFunction)} for
	 * matching input and output types.
	 * @param col A List of data to mutate
	 * @param trans A function that hypothetically modifies the internal state of an object
	 * @param <T> The type of the input elements
	 */
	@SuppressWarnings("null")
	public static <T> void transform(List<T> col, UnaryOperator<T> trans) {
		transform(col.listIterator(), trans);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. <br>
	 * Specializes the filter function with a return type of Set
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A Set of data to be filtered
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	@SuppressWarnings("null")
	public static <T> Set<T> filter(Set<T> col, Predicate<T> pred) {
		return Objects.firstNonNull((Set<T>) filter(((Collection<T>) col), col.getClass(), pred), 
				Collections.EMPTY_SET);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. <br>
	 * Specializes the filter function with a return type of List
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A List of data to be filtered
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	@SuppressWarnings("null")
	public static <T> List<T> filter(List<T> col, Predicate<T> pred) {
		return Objects.firstNonNull((List<T>) filter(((Collection<T>) col), col.getClass(), pred), 
				Collections.EMPTY_LIST);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. 
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A Collection of data to be filtered
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	@SuppressWarnings("null")
	public static @Nullable <T> Collection<T> filter(Collection<T> col, Predicate<T> pred) {
		return filter(col, col.getClass(), pred);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. <br>
	 * Specializes the filter function with a return type of List
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A List of data to be filtered
	 * @param type The return class to be instantiated
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	public static @Nullable <T> List<T> filter(List<T> col, 
			Class<? extends List<T>> type, Predicate<T> pred) {
		return (List<T>) filter(((Collection<T>) col), type, pred);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. <br>
	 * Specializes the filter function with a return type of Set
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A Set of data to be filtered
	 * @param type The return class to be instantiated
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	public static @Nullable <T> Set<T> filter(Set<T> col, 
			Class<? extends Set<T>> type, Predicate<T> pred) {
		return (Set<T>) filter(((Collection<T>) col), type, pred);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. 
	 * @see {@link Collect#filter(Iterator, Class, Predicate)}
	 * @param col A Collection of data to be filtered
	 * @param type The return class to be instantiated
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	@SuppressWarnings({ "null", "rawtypes" })
	public static @Nullable <T> Collection<T> filter(Collection<T> col, 
			Class<? extends Collection> type, Predicate<T> pred) {
		return filter(col.iterator(), type, pred);
	}
	
	/**
	 * filter :: ('a collection) -> ('a -> boolean) -> ('a collection) <br>
	 * Filters the contents of a collection, removing all entries which do not meet 
	 * the given predicate without changing the contents of the original collection. 
	 * @param it An Iterator of data to be filtered
	 * @param type The return class to be instantiated
	 * @param pred A function of the form (T -> boolean)
	 * @param <T> The type of the input elements
	 * @return A collection containing only entries that have passes the predicate.
	 */
	@SuppressWarnings({ "null", "rawtypes" })
	public static @Nullable <T> Collection<T> filter(Iterator<T> it, 
			Class<? extends Collection> type, Predicate<T> pred) {
		Collection<T> filtered = null;
		try {
			filtered = type.newInstance();
			while (it.hasNext()) {
				T temp = it.next();
				if (pred.test(temp)) {
					filtered.add(temp);
				}
			}
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		return filtered;
	}
	
	/**
	 * foldRight :: ('a list) -> ('a -> 'a -> 'a) -> 'a -> 'a <br>
	 * Folds a given list of data using right-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "abcd" <br>
	 * @param col A List of data to be folded in reverse order
	 * @param folder A function of the form (T, S) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings("null")
	public static @NonNull <T, S> S foldRight(List<T> col,
			BinaryFunction<T, S, S> folder, S accum) {
		return foldRight(col.listIterator(col.size()), folder, accum);
	}
	
	/**
	 * foldRight :: ('a list) -> ('a -> 'a -> 'a) -> 'a -> 'a <br>
	 * Folds a given list of data using right-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "abcd" <br>
	 * @see {@link Collect#foldRight(ListIterator, BinaryOperator, Object)}
	 * @param it A ListIterator of data to be folded in reverse order
	 * @param folder A function of the form (T, S) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings("null")
	public static @NonNull <T, S> S foldRight(ListIterator<T> it,
			BinaryFunction<T, S, S> folder, S accum) {
		S result = accum;
		while (it.hasPrevious()) {
			T temp = it.previous();
			result = folder.apply(temp, result);
		}
		return result;
	}
	
	/**
	 * foldRight :: ('a list) -> ('a -> 'b -> 'b) -> 'b -> 'b <br>
	 * Folds a given list of data using right-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "abcd" <br>
	 * WARNING!: This version uses bad recursion and may cause a StackOverflowException 
	 * use at your own risk. A better alternative would be to reverse the collection and 
	 * then use foldLeft on it because foldRight(col, (a,b)->c, x) = 
	 * foldLeft(col.reverse, (b,a)->c, x).
	 * @param it An Iterator to be folded in reverse order
	 * @param folder A function of the form (T, S) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings("null")
	public static @NonNull <T, S> S foldRight(Iterator<T> it,
			BinaryFunction<T, S, S> folder, S accum) {
		return it.hasNext() ? folder.apply(it.next(), foldRight(it, folder, accum)) : accum;
	}
	
	/**
	 * foldRight :: ('a list) -> ('a -> 'b -> 'b) -> 'b -> 'b <br>
	 * Folds a given list of data using right-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "abcd" <br>
	 * WARNING!: This version uses bad recursion and may cause a StackOverflowException 
	 * use at your own risk. A better alternative would be to reverse the collection and 
	 * then use foldLeft on it because foldRight(col, (a,b)->c, x) = 
	 * foldLeft(col.reverse, (b,a)->c, x).
	 * @see {@link Collect#foldRight(Iterator, BinaryOperator, Object)}
	 * @param col A Collection to be folded in reverse order
	 * @param folder A function of the form (T, S) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings("null")
	public static @NonNull <T, S> S foldRight(Collection<T> col,
			BinaryFunction<T, S, S> folder, S accum) {
		return foldRight(col.iterator(), folder, accum);
	}
	
	/**
	 * foldRight :: ('a list) -> ('a -> 'b -> 'b) -> 'b -> 'b <br>
	 * Folds a given list of data using right-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "abcd" <br>
	 * Note: This version avoids the StackOverflow problem by copying all elements into a
	 * List object first.
	 * @see {@link Collect#foldRight(ListIterator, BinaryOperator, Object)}
	 * @param col A Collection to be folded in reverse order
	 * @param folder A function of the form (T, S) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings("null")
	public static @NonNull <T, S> S foldRightSafe(Collection<T> col,
			BinaryFunction<T, S, S> folder, S accum) {
		return col instanceof List ? foldRight((List<T>) col, folder, accum) : 
			foldRight(new LinkedList<T>(col), folder, accum);
	}
	
	/**
	 * foldLeft :: ('a list) -> ('b -> 'a -> 'b) -> 'b -> 'b <br>
	 * Folds a given list of data using left-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "dabc"
	 * @see {@link Collect#foldLeft(Iterator, BinaryOperator, Object)}
	 * @param col A Collection to be folded in order
	 * @param folder A function of the form (S, T) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings({ "null" })
	public static @NonNull <T, S> S foldLeft(Collection<T> col, 
			BinaryFunction<S, T, S> folder, S accum) {
		return foldLeft(col.iterator(), folder, accum);
	}
	
	/**
	 * foldLeft :: ('a list) -> ('b -> 'a -> 'b) -> 'b -> 'b <br>
	 * Folds a given list of data using left-associativity <br>
	 * e.g. foldRight(("a", "b", "c"), +, "d") = "dabc" <br>
	 * @param it An Iterator of data to be folded in order
	 * @param folder A function of the form (S, T) -> S
	 * @param accum The initial value
	 * @param <T> The type of the input elements
	 * @param <S> The type of the accumulated elements
	 * @return The folded output
	 */
	@SuppressWarnings({ "null" })
	public static @NonNull <T, S> S foldLeft(Iterator<T> it, 
			BinaryFunction<S, T, S> folder, S accum) {
		S result = accum;
		while (it.hasNext()) {
			T temp = it.next();
			result = folder.apply(result, temp);
		}
		return result;
	}
	
	/**
	 * max :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the maximum element in
	 * a Collection. It uses a Comparator to determine the ordering of elements.
	 * @param col A Collection to search for the maximum element
	 * @param cmp A Comparator that determines which element is greater that/less than
	 * each other.
	 * @param <T> The type of the elements search
	 * @return The maximum element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> max(Collection<T> col, Comparator<T> cmp) {
		return most(col.iterator(), new CompareOperator<T>(cmp, GREATER_THAN_OR_EQUAL));
	}
	
	/**
	 * max :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the maximum element in
	 * a Collection. It uses a Comparator to determine the ordering of elements.
	 * @param it An Iterator to search for the maximum element
	 * @param cmp A Comparator that determines which element is greater that/less than
	 * each other.
	 * @param <T> The type of the elements to search
	 * @return The maximum element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> max(Iterator<T> it, Comparator<T> cmp) {
		return most(it, new CompareOperator<T>(cmp, GREATER_THAN_OR_EQUAL));
	}
	
	/**
	 * min :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the minimum element in
	 * a Collection. It uses a Comparator to determine the ordering of elements.
	 * @param col A Collection to search for the minimum element
	 * @param cmp A Comparator that determines which element is greater that/less than
	 * each other.
	 * @param <T> The type of the elements to search
	 * @return The minimum element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> min(Collection<T> col, Comparator<T> cmp) {
		return most(col.iterator(), new CompareOperator<T>(cmp, LESS_THAN_OR_EQUAL));
	}
	
	/**
	 * min :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the minimum element in
	 * a Collection. It uses a Comparator to determine the ordering of elements.
	 * @param it An Iterator to search for the maximum element
	 * @param cmp A Comparator that determines which element is greater that/less than
	 * each other.
	 * @param <T> The type of the elements to search
	 * @return The minimum element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> min(Iterator<T> it, Comparator<T> cmp) {
		return most(it, new CompareOperator<T>(cmp, LESS_THAN_OR_EQUAL));
	}
	
	/**
	 * most :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the element with the most of some 
	 * quality in a Collection. It uses a Comparator to determine the ordering of elements.
	 * max(col) is an example of most(col, >)
	 * @param col A Collection to search for the most element
	 * @param cmp A function T -> T -> Boolean that declares if the first argument is more than the
	 * second argument in some quality.
	 * @param <T> The type of the elements search
	 * @return The most element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> most(Collection<T> col, 
			BinaryFunction<T, T, Boolean> cmp) {
		return most(col.iterator(), cmp);
	}
	
	/**
	 * most :: ('a list) -> ('a -> 'a -> bool) -> 'a <br>
	 * A specialization of the folding function that returns the element with the most of some 
	 * quality in a Collection. It uses a Comparator to determine the ordering of elements.
	 * max(col) is an example of most(col, >)
	 * @param it An Iterator to search for the most element
	 * @param cmp A function T -> T -> Boolean that declares if the first argument is more than the
	 * second argument in some quality.
	 * @param <T> The type of the elements search
	 * @return The most element of the collection, or {@link Optional#absent()} 
	 * if the collection is empty
	 */
	@SuppressWarnings("null")
	public static @NonNull <T> Optional<T> most(Iterator<T> it, 
			final BinaryFunction<T, T, Boolean> cmp) {
		T ret = null;
		if ((ret = getFirst(it)) != null) {
			ret = foldLeft(it, new BinaryOperator<T>() {
				@Override
				public T apply(T t1, T t2) {
					return cmp.apply(t1, t2) ? t1 : t2;
				}
			}, ret);
		}
		return Optional.fromNullable(ret);
	}
	
	/**
	 * allMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to see if all of them comply with the given 
	 * criteria.
	 * @param col A Collection to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true iff every element of the collection passes the criteria
	 */
	@SuppressWarnings("null")
	public static <T> boolean allMatch(Collection<T> col, Predicate<? super T> criteria) {
		return matchWith(col.iterator(), Predicate.Not.of(criteria), false);
	}
	
	/**
	 * allMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to see if all of them comply with the given 
	 * criteria.
	 * @param it An Iterator to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true iff every element of the collection passes the criteria
	 */
	public static <T> boolean allMatch(Iterator<T> it, Predicate<? super T> criteria) {
		return matchWith(it, Predicate.Not.of(criteria), false);
	}
	
	/**
	 * anyMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to see if any of them comply with the given 
	 * criteria.
	 * @param col A Collection to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true if any single element of the collection passes the criteria
	 */
	@SuppressWarnings("null")
	public static <T> boolean anyMatch(Collection<T> col, Predicate<? super T> criteria) {
		return matchWith(col.iterator(), criteria, true);
	}
	
	/**
	 * anyMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to see if any of them comply with the given 
	 * criteria.
	 * @param it An Iterator to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true if any single element of the collection passes the criteria
	 */
	public static <T> boolean anyMatch(Iterator<T> it, Predicate<? super T> criteria) {
		return matchWith(it, criteria, true);
	}
	
	/**
	 * noneMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to confirm that none of them comply with the given 
	 * criteria.
	 * @param col A Collection to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true iff not one single element of the collection passes the criteria
	 */
	@SuppressWarnings("null")
	public static <T> boolean noneMatch(Collection<T> col, Predicate<? super T> criteria) {
		return matchWith(col.iterator(), criteria, false);
	}
	
	/**
	 * noneMatch :: ('a list) -> ('a -> bool) -> bool <br>
	 * Checks a collection of elements to confirm that none of them comply with the given 
	 * criteria.
	 * @param it An Iterator to check for compliance
	 * @param criteria A Predicate function to check each element
	 * @param <T> The type of elements we are checking
	 * @return true iff not one single element of the collection passes the criteria
	 */
	public static <T> boolean noneMatch(Iterator<T> it, Predicate<? super T> criteria) {
		return matchWith(it, criteria, false);
	}
	
	/**
	 * A helper to combine the algorithms of the *Match functions
	 * @param it The Iterator to check
	 * @param criteria A Predicate function to check each element
	 * @param anyHits The return value should any element match the criteria
	 * @param <T> The type of elements we are checking
	 * @return the value of anyHits iff not one single element of the collection passes 
	 * the criteria
	 */
	private static <T> boolean matchWith(Iterator<T> it, Predicate<? super T> criteria, 
			boolean anyHits) {
		boolean retValue = !anyHits;
		while (it.hasNext()) {
			@SuppressWarnings("null")
			final @NonNull T end = it.next();
			if (criteria.test(end)) {
				retValue = anyHits;
				break;
			}
		}
		return retValue;
	}
	
	/**
	 * Gets the first element from an iterator that is not null
	 * @param it The Iterator to advance
	 * @param <T> The type of elements we are observing
	 * @return The fist non null entry of the iterator, or null if there is no such element
	 */
	private static @Nullable <T> T getFirst(Iterator<T> it) {
		T ret = null;
		while (it.hasNext() && ret == null) {
			ret = it.next();
		}
		return ret;
	}

}
